package main

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"reflect"
	"strings"

	"github.com/gogf/gf/v2/container/gmap"
	"github.com/gogf/gf/v2/encoding/gjson"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gfile"
	"github.com/gogf/gf/v2/text/gstr"
	"github.com/gogf/gf/v2/util/gconv"
)

func MiddlewareCORS(r *ghttp.Request) {
	// corsOptions := r.Response.DefaultCORSOptions()
	// corsOptions.AllowDomain = []string{"http://localhost:8080"}
	// r.Response.CORS(corsOptions)
	r.Response.CORSDefault()
	r.Middleware.Next()
}

// 驼峰转下划线
func humpToUnderline(s string) string {
	data := make([]byte, 0, len(s)*2)
	j := false
	num := len(s)
	for i := 0; i < num; i++ {
		d := s[i]
		// or通过ASCII码进行大小写的转化
		// 65-90（A-Z），97-122（a-z）
		//判断如果字母为大写的A-Z就在前面拼接一个_
		if i > 0 && d >= 'A' && d <= 'Z' && j {
			data = append(data, '_')
		}
		if d != '_' {
			j = true
		}
		data = append(data, d)
	}
	return strings.ToLower(string(data[:]))
}

// 打印结构体
func generateStruct(data interface{}, structName string, nameSpace string, buffers *Buffers) {
	structName = line2camel(structName)
	v := reflect.ValueOf(data)
	var buffer bytes.Buffer
	var tableSqlBuffer bytes.Buffer
	var insetSqlBuffer bytes.Buffer
	buffer.WriteString("\ntype ")
	buffer.WriteString(structName)
	buffer.WriteString(" struct {\n")
	tableSqlBuffer.WriteString("\ncreate table if not exists hg_mcard_" + nameSpace + humpToUnderline(structName) + " ( \n`id` bigint(20) NOT NULL auto_increment,\n")
	insetSqlBuffer.WriteString("\ninsert into hg_mcard_" + nameSpace + humpToUnderline(structName) + " set ")
	switch v.Kind() {
	case reflect.Slice, reflect.Array:
		vslice := data.([]interface{})
		if len(vslice) > 0 {
			switch reflect.ValueOf(vslice[0]).Kind() {
			case reflect.Map, reflect.Slice, reflect.Array:
				sname := fmt.Sprintf("%s%s", nameSpace, "List")
				generateStruct(vslice[0], sname, nameSpace, buffers)
				buffer.WriteString("[]" + line2camel(sname))
			default:
				buffer.WriteString("[]" + reflect.ValueOf(vslice[0]).Kind().String())
			}
		}
	case reflect.Map:
		for k, m := range data.(map[string]interface{}) {
			buffer.WriteString(line2camel(k) + "\t")
			switch reflect.ValueOf(m).Kind() {
			case reflect.Array, reflect.Slice:
				sname := fmt.Sprintf("%s%s", nameSpace, k)
				vslice := m.([]interface{})
				if len(vslice) > 0 {
					switch reflect.ValueOf(vslice[0]).Kind() {
					case reflect.Map, reflect.Slice, reflect.Array:
						generateStruct(vslice[0], sname, nameSpace, buffers)
						buffer.WriteString("[]" + line2camel(sname))
					default:
						buffer.WriteString("[]" + reflect.ValueOf(vslice[0]).Kind().String())
					}
				} else {
					buffer.WriteString(line2camel(sname))
					generateStruct([]interface{}{}, sname, nameSpace, buffers)
				}
			case reflect.Map:
				sname := fmt.Sprintf("%s%s", nameSpace, k)
				generateStruct(m, sname, nameSpace, buffers)
				buffer.WriteString(line2camel(sname))
			default:
				itype := reflect.ValueOf(m).Kind().String()
				if itype == "invalid" {
					itype = "string"
				}
				buffer.WriteString(itype)
				sitype := "varchar(32)"
				dc := ""
				if itype == "float64" || itype == "int" {
					sitype = "int(11)"
					dc = fmt.Sprintf("%.0f", m)
				}
				if itype == "string" {
					sitype = "varchar(255)"
					dc = fmt.Sprintf("%s", m)
				}
				if k != "id" {
					tableSqlBuffer.WriteString("`" + humpToUnderline(k) + "` " + sitype + " NOT NULL DEFAULT '" + dc + "' COMMENT '',\n")
					insetSqlBuffer.WriteString("`" + humpToUnderline(k) + "` = '" + gconv.String(m) + "',")
				}

			}
			buffer.WriteString("\t`json:\"")
			buffer.WriteString(humpToUnderline(k))
			buffer.WriteString("\"`")
			buffer.WriteString("\n")
		}
	}
	buffer.WriteString("}\n")
	tableSqlBuffer.WriteString("PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='';\n")
	buffers.Data += buffer.String()
	buffers.Sql += tableSqlBuffer.String() + insetSqlBuffer.String()[0:len(insetSqlBuffer.String())-1] + ";"
}

type Buffers struct {
	Sql  string
	Data string
}

func fixRoute(route string) string {
	r := gstr.Split(route, "/")
	rs := []string{}
	for i := range r {
		if r[i] != "" {
			rs = append(rs, r[i])
		}
	}
	route = gstr.Join(rs, "/")
	return route
}
func line2camel(route string) string {
	splits := gstr.Split(route, "_")
	for i := range splits {
		splits[i] = gstr.UcFirst(splits[i])
	}
	return gstr.Join(splits, "")
}
func reqTpl(reqName string, route string, r *ghttp.Request) string {
	method := r.Method
	params := r.GetMap()
	body := r.GetBody()
	data, _ := gjson.DecodeToJson(body)
	newMap := gmap.NewStrAnyMap(true)
	for key, value := range params {
		newMap.Set(key, value)
	}
	for key, value := range data.Map() {
		newMap.Set(key, value)
	}
	// 请求模板
	reqTpl := "\ntype " + reqName + "Req struct {"
	reqTpl += "\n\tg.Meta `path:\"" + route + "\" method:\"" + method + "\" summary:\"\"`"

	for key, value := range newMap.Map() {
		if key == "url" {
			continue
		}
		reqTpl += "\n\t" + line2camel(key) + " string `json:\"" + key + "\" dc:\"" + gconv.String(value) + "\"`"
	}
	reqTpl += "\n}"
	return reqTpl
}
func controllerTpl(namespace string, className string) string {
	//controller模板
	tpl := "package " + namespace + "\n"
	tpl += "\nimport \"context\""
	tpl += "\nimport \"github.com/gogf/gf/v2/encoding/gjson\""
	tpl += "\nimport \"github.com/gogf/gf/v2/frame/g\""

	tpl += `/*
s.Group("/", func(group *ghttp.RouterGroup) {
	group.Group("/", func(group *ghttp.RouterGroup) {
		group.Bind(controller.` + className + `)
	})
})
*/`
	tpl += "\nvar " + className + " = c" + className + "{}"
	tpl += "\ntype c" + className + " struct{}"
	return tpl
}

func funcTpl(reqName string, className string, actionName string, jsonStr string) string {
	var jsonObj map[string]interface{}
	json.Unmarshal([]byte(jsonStr), &jsonObj)
	// 方法模板
	atpl := "\nfunc (a *c" + className + ") " + actionName + "(ctx context.Context, req *" + reqName + "Req) (res *" + reqName + "Res, err error) {"
	atpl += "\n\tres = &" + reqName + "Res{}"
	atpl += "\n\tdata:=`" + gconv.String(jsonObj["data"]) + "`"
	atpl += "\n\tjs:=gjson.New(data)"
	atpl += "\n\tjs.Scan(&res)"
	atpl += "\n\treturn"
	atpl += "\n}"
	return atpl
}
func curl(r *ghttp.Request, url string, ctx context.Context) (jsonStr string) {

	// url := "https://scrm.asphel.cn/addons/wlkwash/" + route
	header := r.Header
	method := r.Method
	client := g.Client()
	client.SetHeader("Content-Type", "application/json")
	client.SetHeader("token", header.Get("token"))
	params := r.GetMap()
	body := r.GetBody()
	data, _ := gjson.DecodeToJson(body)
	newMap := gmap.NewStrAnyMap(true)
	for key, value := range params {
		newMap.Set(key, value)
	}
	for key, value := range data.Map() {
		newMap.Set(key, value)
	}

	if url != "" {
		if method == "POST" {
			files := r.GetUploadFiles("file")
			if files != nil {
				names, _ := files.Save("/tmp/")
				path := "/tmp/" + names[0]
				jsonStr = client.PostContent(ctx, url, "file=@file:"+path)
			} else {
				jsonStr = client.PostContent(ctx, url, newMap)
			}
		} else if method == "GET" {
			jsonStr = client.GetContent(ctx, url)
		}
	}
	return
}

func resTpl(namespace string, reqName string, jsonStr string) Buffers {
	var jsonObj map[string]interface{}
	json.Unmarshal([]byte(jsonStr), &jsonObj)
	var resTpl Buffers
	generateStruct(jsonObj["data"], reqName+"Res", namespace+"_", &resTpl)
	return resTpl
}

/*
### 代码功能解释

这段Go代码实现了一个代理功能，主要步骤如下：

1. **获取请求参数**：从请求中获取URL和其他参数。
2. **构建目标URL**：根据获取的URL构建目标URL。
3. **生成文件路径和类名**：根据URL生成控制器文件路径、类名和方法名。
4. **解析请求参数**：将请求参数解析为结构体模板。
5. **生成模板代码**：生成控制器、请求结构体、响应结构体和方法的模板代码。
6. **发送请求**：根据请求方法（GET或POST）发送请求，并处理响应。
7. **写入文件**：将生成的模板代码写入相应的文件。
8. **返回响应**：将响应结果返回给客户端。

### 控制流图

```mermaid
flowchart TD

	A[开始] --> B[获取请求参数]
	B --> C[构建目标URL]
	C --> D[生成文件路径和类名]
	D --> E[解析请求参数]
	E --> F[生成模板代码]
	F --> G[发送请求]
	G --> H{请求方法是POST?}
	H -->|Yes| I{有上传文件?}
	I -->|Yes| J[处理文件上传并发送POST请求]
	I -->|No| K[发送POST请求]
	H -->|No| L[发送GET请求]
	J --> M[处理响应]
	K --> M[处理响应]
	L --> M[处理响应]
	M --> N[写入文件]
	N --> O[返回响应]
	O --> P[结束]

```

### 详细解释

1. **获取请求参数**：
  - 从请求中获取URL和其他参数。
  - 构建目标URL。

2. **生成文件路径和类名**：
  - 根据URL生成控制器文件路径、类名和方法名。

3. **解析请求参数**：
  - 将请求参数解析为结构体模板。

4. **生成模板代码**：
  - 生成控制器、请求结构体、响应结构体和方法的模板代码。

5. **发送请求**：
  - 根据请求方法（GET或POST）发送请求。
  - 处理响应。

6. **写入文件**：
  - 将生成的模板代码写入相应的文件。

7. **返回响应**：
  - 将响应结果返回给客户端。
*/
func Proxy(r *ghttp.Request) {
	host := "https://mp.wanhedashuju.com/"
	path := fixRoute(r.URL.Path)
	route := gstr.Replace(path, "api/", "")
	sqlFileName := "sql/" + route + ".sql"
	jsonFileName := "json/" + route + ".json"
	jsonStr := `{"code": 1, "msg": "", "data": {}}`
	splits := gstr.Split(route, "/")
	fileName := "controller/" + splits[0] + "/" + splits[0] + ".go"
	// 写入文件
	if gfile.IsFile(jsonFileName) {
		jsonStr = gfile.GetContents(jsonFileName)
	} else {
		jsonStr = curl(r, host+path, r.GetCtx())
		gfile.PutContents(jsonFileName, jsonStr)
	}
	namespace := splits[0]
	className := gstr.UcFirst(line2camel(splits[0])) + "Controller"
	actionName := gstr.UcFirst(line2camel(splits[1])) + "Action"
	reqName := line2camel(splits[1])
	//controller模板
	tpl := controllerTpl(namespace, className)
	// 请求模板
	reqTpl := reqTpl(reqName, route, r)
	// 响应模板
	resTpl := resTpl(namespace, reqName, jsonStr)
	// 方法模板
	funcTpl := funcTpl(reqName, className, actionName, jsonStr)

	// 写入文件
	if gfile.IsFile(fileName) {
		content := gfile.GetContents(fileName)
		str := "func (a *c" + className + ") " + actionName + "("
		if gstr.Pos(content, str) == -1 {
			gfile.PutContentsAppend(fileName, reqTpl+resTpl.Data+funcTpl)
		}
	} else {
		gfile.PutContents(fileName, tpl+reqTpl+resTpl.Data+funcTpl)
	}
	if !gfile.IsFile(sqlFileName) {
		gfile.PutContents(sqlFileName, resTpl.Sql)
	}

	routerContent := gfile.GetContents("controller/router.go")
	if gstr.Pos(routerContent, className) == -1 {
		routeTpl := "\tNoLoginRouter = append(NoLoginRouter, " + namespace + "." + className + ")"
		routeTpl += "\n// ......"
		gfile.PutContents("controller/router.go", gstr.Replace(routerContent, "// ......", routeTpl))
	}

	r.Response.Write(jsonStr)
}
func main() {

	s := g.Server()
	// 注册全局中间件
	// s.BindMiddleware("/*any", []ghttp.HandlerFunc{
	// 	MiddlewareCORS,
	// }...)
	//s.BindMiddlewareDefault(MiddlewareCORS)
	// s.BindHandler("/get", Proxy)
	s.Group("/", func(group *ghttp.RouterGroup) {
		group.Middleware(MiddlewareCORS)
		group.ALL("/api/*any", Proxy)
	})
	s.SetPort(8000)
	s.Run()
}
